const buildURL = (base, url) => {
    if (!url || !base || /^http(s?):\/\//i.test(url)) {
        return url || '';
    }
    return `${base.replace(/\/$/, '')}/${url.replace(/^\//, '')}`;
};
const textTypes = new Set([
    'image/svg',
    'application/xml',
    'application/xhtml',
    'application/html',
]);
const jsonReg = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i;
function detectResponseType(cType) {
    if (!cType) {
        return 'json';
    }
    const contentType = cType.split(';').shift() || '';
    if (jsonReg.test(contentType)) {
        return 'json';
    }
    if (textTypes.has(contentType) || contentType.startsWith('text/')) {
        return 'text';
    }
    return 'blob';
}
class Interceptors {
    constructor() {
        Object.defineProperty(this, "requestHandlers", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: []
        });
        Object.defineProperty(this, "responseHandlers", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: []
        });
    }
    async requestEach(config) {
        let conf = config;
        for (const handler of this.requestHandlers) {
            if (handler) {
                conf = await handler(conf);
            }
        }
        return conf;
    }
    request(...args) {
        this.requestHandlers.push(...args);
    }
    async responseEach(response) {
        let respond = response;
        for (const handler of this.responseHandlers) {
            if (handler) {
                respond = await handler(respond);
            }
        }
        return respond;
    }
    response(...args) {
        this.responseHandlers.push(...args);
    }
}
class IFetch {
    constructor(baseURL, timeout, credentials) {
        Object.defineProperty(this, "baseURL", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: ''
        });
        Object.defineProperty(this, "timeout", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 10 * 1000
        });
        Object.defineProperty(this, "credentials", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "interceptors", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: new Interceptors()
        });
        if (baseURL) {
            this.baseURL = baseURL;
        }
        if (timeout) {
            this.timeout = timeout;
        }
        if (credentials) {
            this.credentials = credentials;
        }
    }
    async request(options) {
        if (!options.url) {
            throw new Error('options.url not define!');
        }
        const { url: _url, method, params, data, responseType, timeout, headers, ...rest } = options;
        let url = buildURL(this.baseURL, _url);
        const queries = params || {};
        if (queries && typeof queries === 'object') {
            const [start, end] = url.split('?');
            const search = new URLSearchParams(end);
            Object.keys(queries).forEach(key => search.append(key, queries[key]));
            url = `${start}?${search.toString()}`;
        }
        const abortInstance = new AbortController();
        const fetchOptions = {
            ...rest,
            method,
            signal: abortInstance.signal,
            headers: new Headers(headers || {})
        };
        if (this.credentials) {
            fetchOptions.credentials = this.credentials;
        }
        const postData = data;
        if (postData && method && !['GET', 'HEAD'].includes(method)) {
            const dataType = Object.prototype.toString.call(postData);
            if ([
                '[object FormData]',
                '[object Blob]',
                '[object File]',
            ].includes(dataType)) {
                fetchOptions.body = postData;
            }
            else {
                fetchOptions.body = typeof postData === 'string'
                    ? postData
                    : JSON.stringify(postData);
            }
        }
        const config = await this.interceptors.requestEach(fetchOptions);
        const timer = setTimeout(() => abortInstance.abort(), timeout || this.timeout);
        const response = await fetch(url, config);
        clearTimeout(timer);
        const respondType = responseType || detectResponseType(response.headers.get('Content-type'));
        const back = async (respondType, response) => {
            switch (respondType) {
                case 'text':
                    return await response.text();
                case 'blob':
                    return await response.blob();
                case 'arrayBuffer':
                    return await response.arrayBuffer();
                case 'stream':
                    return response.body;
                case 'json':
                default:
                    return await response.json();
            }
        };
        const { status, statusText } = response;
        const responseData = await back(respondType, response);
        const respond = {
            status,
            statusText,
            data: responseData,
            options: {
                url: response.url
            }
        };
        const result = await this.interceptors.responseEach(respond);
        if (status >= 200 && status < 400) {
            return result;
        }
        else {
            throw result;
        }
    }
    get(url, options) {
        return this.request({ ...options, url, method: 'GET' });
    }
    post(url, options) {
        return this.request({ ...options, url, method: 'POST' });
    }
    put(url, options) {
        return this.request({ ...options, url, method: 'PUT' });
    }
    delete(url, options) {
        return this.request({ ...options, url, method: 'DELETE' });
    }
}

export { IFetch as default };
